메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

리눅스 디바이스 드라이버 심플 소개(2)

한빛미디어

|

2007-08-21

|

by HANBIT

19,925

제공 : 한빛 네트워크
저자 : Valerie Henson
역자 : 노재현
원문 : /dev/hello_world: A Simple Introduction to Device Drivers under Linux

[이전 기사 보기]
리눅스 디바이스 드라이버 심플 소개(1)

/dev/hello_world 를 이용한 "Hello, World!"

이제 /dev/hello_world라는 /dev에 있는 파일을 이용해서 "Hello, world!"를 만들어 보도록 하겠습니다. 오래전부터 디바이스 파일은 해당 디바이스 파일이 제어하게될 장치가 실제로 존재하느냐 안 하느냐에 관계없이 MAKEDEV 라는 쉘 스크립트를 통해서 mknod 명령어로 만들어진 특수한 파일이었습니다. 그 뒤를 이어 사용되게 된 devfs는 최초로 엑세스 되었을때 /dev 파일을 생성하게끔 되어 있었고, 이 기능은 많은 단점들을 가지고 오게 되었습니다. Locking과 관련된 문제점, 해당 장치가 존재하는지 확인하기 위해서 불필요하게 파일을 열려고 시도를 하는 문제점 등이 바로 그것이였습니다. 현재에 사용하고 있는 udev라고 불리우는데 바로 이 udev가 유저 프로그램과 소통할 수 있는 /dev 링크를 만들어 줄 수 있기 때문입니다. 커널 모듈이 장치를 등록하게 되면 그 장치들이 /sys 디렉토리에 마운트 된 sysfs 파일시스템에 나타나게 되고, 등록된 장치에서 변화가 발생하게 되면 udev 프로그램이 /etc/udev 에 지정된 규칙에 따라서 /dev 에 파일을 생성해 주게 됩니다.

hello world module tarball 을 다운로드 받고, hello_dev.c 파일을 보도록 하겠습니다.
#include  
#include 
#include 
#include  
 
#include 
헤더 파일을 보니 이전의 모듈과는 달리 커널의 기능을 더 많이 필요로 한다는 걸 알 수 있습니다. fs.h 파일은 파일 연산 관련 구조체에 대한 정의를 담고 있는 파일로써, 이 파일 연상 구조체에 내용을 저장한 후에 나중에 /dev 파일을 만들때 지정해주어야 합니다. miscdevice.h 파일은 기타 장치 파일을 등록하는데 필요한 함수를 포함하고 있습니다. asm/uaccess.h 파일은 유저 스페이스 메모리에 쓰거나 읽을때 해당 연산이 가능한지 아닌지를 테스트할때 사용하는 함수들을 포함하고 있습니다.

다음 hello_read() 함수는 /dev/hello 파일에 읽기 명령이 수행되었을때 호출되는 함수로써 read() 함수에 인자로 전달된 버퍼에 "Hello, world!"라는 문자열을 저장하는 기능을 합니다.
static ssize_t hello_read(struct file * file, char * buf, 
                          size_t count, loff_t *ppos)
{
        char *hello_str = "Hello, world!n";
        int len = strlen(hello_str); /* 비어있는 문자열인지 확인하기 위해서 길이를 계산한다. */
        /*
         * 전체 스트링을 한 번에 읽어들이는 연산만 허용한다.
         */
        if (count < len)
                return -EINVAL;
        /*
         * position 값이 0이 아니면,
         * 더 이상 읽을 데이터가 없는 것으로 간주한다.
         */
        if (*ppos != 0)
                return 0;
        /*
         * 문자열을 유저 버퍼에 쓰는 것 외에
         * 해당 유저가 버퍼에 쓸 수 있는 권한이 있는지도 확인한다.
         * 
         */
        if (copy_to_user(buf, hello_str, len))
                return -EINVAL;
        /*
         * 진행상황을 유저에게 알려준다.
         */
        *ppos = len;

        return len;
}
이제 각 파일 연산이 들어왔을때 어떤 일을 해야하는지를 지정하는 구조체를 만들어 보겠습니다. 여기서는 읽기 연산만 구현해 볼 것입니다.
static const struct file_operations hello_fops = {
        .owner                = THIS_MODULE,
        .read                = hello_read,
};
이제 장치를 커널에 등록하는데 필요한 정보를 담고 있는 구조체를 만들어 보겠습니다.
static struct miscdevice hello_dev = {
        /*
         * MINOR 번호를 직접 지정하지 않고
         * 커널이 선택하도록 한다.
         */
        MISC_DYNAMIC_MINOR,
        /*
         * 장치의 이름을 hello로 한다.
         */
        "hello",
        /*
         * 파일 연산을 할 때 호출되게 될
         * 함수를 정의
         */
        &hello_fops
};
언제나 마찬가지로 모듈의 초기화 함수에서 장치를 등록하겠습니다.
static int __init
hello_init(void)
{
        int ret;

        /*
         * sys/class/misc 디렉토리에 /dev/hello 장치를 만든다.
         * udev가 자동적으로 정해진 규칙에 따라서 /dev/hello 장치를 
         * 만들게 된다.
         */
        ret = misc_register(&hello_dev);
        if (ret)
                printk(KERN_ERR
                       "Unable to register "Hello, world!" misc devicen");

        return ret;
}

module_init(hello_init);
그리고 exit 함수에서 장치 등록을 해제하는 것도 잊으면 안됩니다.
static void __exit
hello_exit(void)
{
        misc_deregister(&hello_dev);
}

module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Valerie Henson ");
MODULE_DESCRIPTION(""Hello, world!" minimal module");
MODULE_VERSION("dev");
이제 컴파일 하고 모듈을 로딩해 보겠습니다.
$ cd hello_dev
$ make
$ sudo insmod ./hello_dev.ko
이제 /dev/hello 라는 장치가 생성되었고, root 유저로 파일을 읽었을때 "Hello, world!"라는 메시지가 출력되게 됩니다.
$ sudo cat /dev/hello
Hello, world!
하지만 일반 유저로는 이 장치를 읽을 수가 없습니다.
$ cat /dev/hello
cat: /dev/hello: Permission denied
$ ls -l /dev/hello
crw-rw---- 1 root root 10, 61 2007-06-20 14:31 /dev/hello
이건 udev의 기본 규칙때문에 일반 유저는 엑세스 할 수가 없게 된 것입니다. 원래 udev는 장치가 생성되면 /dev/<장치 이름>의 파일을 /dev 디렉토리에 만들고 0660의 권한(주인과 그룹은 읽고 쓰기 가능, 그 외에는 접근 불가능)을 주도록 되어 있기때문입니다. 우리가 일반 유저도 읽을 수 있게 만들고 싶기 때문에 다음과 같이 udev의 규칙을 변경해 보도록 하겠습니다.

두 가지를 해야 합니다: 심볼릭 링크를 생성하고 장치의 퍼미션을 모두가 읽을 수 있도록 설정합니다. 이 두 가지 명령을 수행하려면
KERNEL=="hello", SYMLINK+="hello_world", MODE="0444"
위의 규칙을 하나씩 살펴보겠습니다.

KERNEL=="hello"는 /sys 디렉토리에 "hello"라는 이름의 새로운 장치가 생성되었을때 이 명령 다음의 명령들을 수행하라고 하는 뜻 입니다. hello 장치는 misc_register 함수에게 "hello" 라는 장치를 생성하라고 호출을 할때 생성됩니다. /sys 디렉토리에 실제로 장치가 생성되었는지를 보겠습니다.
$ ls -d /sys/class/misc/hello/
/sys/class/misc/hello/
SYMLINK+="hello_world"는 장치가 생성될 때 생성되어야 하는 심볼릭 링크의 리스트를 가지고 있는 곳에 "hello_world"를 추가하는 명령어 입니다. 물론 이번 모듈에서는 심볼릭 링크를 하나만 만들기 때문에 += 연산자 대신에 = 연산자를 쓸 수도 있지만, 나중에 복잡한 장치를 만들때는 여러개의 심볼릭 링크를 만들 수도 있기때문에 연습삼아서 +=로 해보도록 하겠습니다.

MODE="0444"는 파일의 소유자, 그룹, 그외 모든 유저들이 읽을 수 있도록 하는 권한을 부여하는 명령입니다.

여기서 중요한 건 어떤 연산자를 쓸지 잘 결정해야 한다는 것입니다(==, +=, 혹은 =). 실수를 하게 되면 예상치 못한 결과가 발생할 수도 있습니다.

이제 규칙에 대해서 배워봤으니 /etc/udev 디렉토리에 규칙을 추가해 보도록 하겠습니다. Udev 규칙 파일들은 System V에서 /etc/init.d 에 있는 초기화 파일과 마찬가지로 정리되어 있습니다. Udev는 udev 규칙 디렉토리(etc/udev/rules.d)에 있는 모든 스크립트를 알파벳 순서에 따라서 실행하게 됩니다. System V의 초기화 스크립트와 마찬가지로 /etc/udev/rules.d 디렉토리에 있는 파일은 보통 실제 파일에 대한 심볼릭 링크로 되어 있습니다.

hello_dev 디렉토리에 있는 hello.rules 파일을 /etc/udev/ 디렉토리에 복사하고, 복사된 파일에 대한 링크를 만들도록 합니다.
$ sudo cp hello.rules /etc/udev/
$ sudo ln -s ../hello.rules /etc/udev/rules.d/010_hello.rules
그럼 이제 hello world 드라이버를 다시 로딩하고 새로 만들어지는 /dev 파일을 보겠습니다.
$ sudo rmmod hello_dev
$ sudo insmod ./hello_dev.ko
$ ls -l /dev/hello*
cr--r--r-- 1 root root 10, 61 2007-06-19 21:21 /dev/hello
lrwxrwxrwx 1 root root      5 2007-06-19 21:21 /dev/hello_world -> hello
/dev/hello_world 가 생성된 걸 확인할 수 있습니다. 일반 유저로 장치에서 읽을 수 있는지를 확인해 봅시다.
$ cat /dev/hello_world
Hello, world!
$ cat /dev/hello
Hello, world!
udev 규칙에 관한 더 자세한 내용은 Daniel Drake님이 쓴 Writing udev rules 를 참고해 주시면 좋겠습니다.
TAG :
댓글 입력
자료실

최근 본 책0